(function () {
  const intervalMethodTimers = {}

  function Lib () {
    this.members = []
    this.timeout = 30000
    this.loadedLibCount = 0
    this.el = '#app'
    this.loadedLibs = []
    this.env = 'development'
    this.data = {
      $eventParams: '',
      page: {
        minWidth: '0px',
        paddingTop: '0px',
        paddingRight: '0px',
        paddingBottom: '0px',
        paddingLeft: '0px',
        backgroundColor: '#f5f5f5',
        backgroundImage: [],
        backgroundSize: 'auto'
      },
      apis: {}
    }
    this.mounted = false
    this.autoFit = {
      enable: false,
      baseWidth: '1920px',
      baseHeight: '1080px'
    }
    this.dataList = []
    this.libsHtml = []
    this.globalData = {}
    this.pageParams = []
    this.pageParamsResult = {}

    this.computeSize = function (value, type) {
      return value
    }

    this.runIntervalMethod = function (time, name) {
      this[name]()

      intervalMethodTimers[name] = setTimeout(() => {
        this.runIntervalMethod(time, name)
      }, time)
    }

    this.clearIntervalMethod = function () {
      for (const name in intervalMethodTimers) {
        if (intervalMethodTimers.hasOwnProperty(name)) {
          const timer = intervalMethodTimers[name]
          clearTimeout(timer)
        }
      }
    }
  }

  Lib.prototype.initNext = function () {
    window.$twApp.init._done = true
    window.$lib.mounte()
  }

  Lib.prototype.add = function (name, libBaseUrl) {
    const link = document.createElement('link')
    link.href = `${libBaseUrl}/${name}/index.css?v=${Date.now()}`
    link.rel = 'stylesheet'
    link.type = 'text/css'
    document.querySelector('head').appendChild(link)

    const script = document.createElement('script')
    script.src = `${libBaseUrl}/${name}/index.umd.min.js?v=${Date.now()}`
    document.body.appendChild(script)
  }

  Lib.prototype.register = function (name, component) {
    window.Vue.component(name, component)

    this.loadedLibs.push(name)
    this.loadedLibCount++

    if (this.unitLibCount) {
      if (this.loadedLibCount >= this.unitLibCount) {
        this.mounte()
      }
    } else {
      if (this.loadedLibCount >= this.members.length) {
        this.mounte()
      }
    }
  }

  Lib.prototype.genHtml = function () {
    let template = ''

    for (var i = 0; i < this.libsHtml.length; i++) {
      template += window.decodeURIComponent(window.atob(this.libsHtml[i]))
    }

    const tempHtmls = {}

    template = template.replace(/<%= (\w+) ([\s\S]*?) %>/gm, function (match, p1, p2) {
      if (!tempHtmls[p1]) {
        tempHtmls[p1] = ''
      }

      tempHtmls[p1] += p2

      return ''
    })

    for (const key in tempHtmls) {
      if (tempHtmls.hasOwnProperty(key)) {
        const tempHtml = tempHtmls[key]
        const placeholder = `<!-- ${key} -->`
        const parentPlaceholderRegExp = new RegExp(placeholder, 'gm')
        template = template.replace(parentPlaceholderRegExp, tempHtml)
      }
    }

    if (this.el instanceof Element) {
      this.el.innerHTML = template
    } else {
      document.querySelector(this.el).innerHTML = template
    }
  }

  Lib.prototype.addGlobalProp = function (dataObj) {
    const data = JSON.parse(JSON.stringify(dataObj))

    for (const [key, libData] of Object.entries(data)) {
      if (key.startsWith('lib')) {
        if (libData.sourceLibId) {
          const sourceLib = this.$ui.arr.findMode(this.globalLibList, { fileName: libData.sourceLibId })[0]
          const sourceData = JSON.parse(sourceLib.content.data)[libData.sourceLibId]
          Object.assign(data[key], sourceData, data[key].selfData)
        }
      }
    }

    return data
  }

  Lib.prototype.genPageStyle = function (target, pageData) {
    if (pageData) {
      const result = {}

      if (pageData.minWidth && pageData.minWidth !== '0px') result.minWidth = pageData.minWidth
      if (pageData.paddingTop && pageData.paddingTop !== '0px') result.paddingTop = pageData.paddingTop
      if (pageData.paddingRight && pageData.paddingRight !== '0px') result.paddingRight = pageData.paddingRight
      if (pageData.paddingBottom && pageData.paddingBottom !== '0px') result.paddingBottom = pageData.paddingBottom
      if (pageData.paddingLeft && pageData.paddingLeft !== '0px') result.paddingLeft = pageData.paddingLeft
      if (pageData.backgroundColor && pageData.backgroundColor !== '#f5f5f5') result.backgroundColor = pageData.backgroundColor
      if (pageData.backgroundSize && pageData.backgroundSize !== 'auto') result.backgroundSize = pageData.backgroundSize

      if (Array.isArray(pageData.backgroundImage) && pageData.backgroundImage.length > 0) {
        result.backgroundImage = 'url("' + window.$twApp.fileBaseUrl + pageData.backgroundImage[0].localPath + '")'
      } else if (typeof pageData.backgroundImage === 'string' && pageData.backgroundImage) {
        result.backgroundImage = pageData.backgroundImage
      }

      Object.assign(target.page, result)
    }
  }

  Lib.prototype.setBodyStyle = function () {
    let body = document.body

    if (this.env === 'design') {
      body = document.querySelector('.js-pagepreview-body-style-box')
    }

    for (const key in this.data.page) {
      if (body) {
        body.style[key] = this.data.page[key]
      }
    }
  }

  Lib.prototype.addData = function (target, data) {
    Object.assign(target.apis, data.apis)
    this.genPageStyle(target, data.page)

    if (this.env === 'design') {
      this.setBodyStyle()
    }

    for (const key in data) {
      if (/^lib/.test(key)) {
        target[key] = data[key]
      }
    }
  }

  Lib.prototype.genData = function () {
    for (let i = 0; i < this.dataList.length; i++) {
      const data = this.dataList[i]
      this.addData(this.data, data)
    }
  }

  Lib.prototype.addGlobalProp = function (dataObj) {
    const data = JSON.parse(JSON.stringify(dataObj))

    for (const [key, libData] of Object.entries(data)) {
      if (key.startsWith('lib')) {
        if (libData.sourceLibId) {
          Object.assign(data[key], this.globalData[libData.sourceLibId], data[key].selfData)
        }
      }
    }

    return data
  }

  /* eslint-disable no-new-func */
  Lib.prototype.genFunction = function () {
    for (const key in this.data) {
      if (this.data.hasOwnProperty(key)) {
        if (/^lib/.test(key)) {
          const data = this.data[key]

          for (const key2 in data) {
            if (data.hasOwnProperty(key2)) {
              if (/^\$fs/.test(key2)) {
                const value = data[key2]
                const sourceKey = key2.replace(/^\$fs/, '')

                if (value) {
                  try {
                    data[sourceKey] = (new Function('return ' + value))()
                  } catch (e) {
                    console.error(e)
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  Lib.prototype._fitPage = function () {
    if (window.$siteConfig && window.$siteConfig.type) {
      var me = this

      var fn = function (baseWidth, baseHeight) {
        var clientWidth = document.documentElement.clientWidth
        var clientHeight = document.documentElement.clientHeight
        var wRate = clientWidth / parseInt(baseWidth)
        var hRate = baseHeight ? (clientHeight / parseInt(baseHeight)) : wRate

        for (var libKey in me._originalData) {
          if (me._originalData.hasOwnProperty(libKey) && /^lib/.test(libKey)) {
            var libData = me[libKey]

            for (var key in libData) {
              if (libData.hasOwnProperty(key)) {
                var value = me._originalData[libKey][key]
                var rate = wRate

                if (['_height', '_top', '_bottom', '_marginTop', '_marginBottom'].indexOf(key) !== -1) {
                  rate = hRate
                }

                if (typeof value === 'string' && /^\d+px$/.test(value)) {
                  libData[key] = parseInt(parseInt(value) * rate) + 'px'
                }
              }
            }
          }
        }
      }

      if (window.$siteConfig.type.length === 1 && window.$siteConfig.type[0] === 'mobile') {
        if (window.$lib.autoFit.enable) {
          initFont(window.$lib.autoFit.baseWidth || 375)
          fn(window.$lib.autoFit.baseWidth || 375)
        }
      } else {
        if (window.$lib.autoFit.enable) {
          initFont(window.$lib.autoFit.baseWidth || 1440)
          fn(window.$lib.autoFit.baseWidth || 1440, window.$lib.autoFit.baseHeight || 900)
        }
      }
    }
  }

  Lib.prototype.addPageParams = function () {
    this.pageParamsResult = {}

    if (this.env === 'product' && window.$siteConfig.globalVariableConfiguration) {
      this.pageParams.unshift(window.btoa(window.$siteConfig.globalVariableConfiguration))
    }

    for (let i = 0; i < this.pageParams.length; i++) {
      Object.assign(this.pageParamsResult, parsePageParams(this.pageParams[i]))
    }

    Object.assign(this.data, this.pageParamsResult)
  }

  Lib.prototype.mounte = function () {
    const me = this

    if (this.env === 'product') {
      if (this.mounted) return

      // 如果应用指定了初始化函数，且初始化函数未处理完成，不做渲染操作
      if (typeof window.$twApp.init === 'function') {
        if (!window.$twApp.init._done) {
          window.$twApp.init(this.initNext)
          return
        }
      }

      this.genData()
      this.data = this.addGlobalProp(this.data)
      this.setBodyStyle()
      this.genHtml()
    }

    this.addPageParams()
    this.genFunction()

    if (this.unitLibCount) {
      if (this.loadedLibCount < this.unitLibCount) {
        return
      }
    } else {
      if (this.loadedLibCount < this.members.length) {
        return
      }
    }

    if (this.env === 'product') {
      if (window.$pageConfig.needLogin === '1' && !window.$twApp.logged) {
        return
      }
    }

    this.createMethod(this.data)

    me.methods.computeSize = this.computeSize
    me.methods.runIntervalMethod = this.runIntervalMethod

    if (me.env === 'product') {
      me.methods._fitPage = this._fitPage
    }

    this.mounted = true

    /* eslint-disable no-new */
    me.vm = new window.Vue({
      el: me.el,
      data: me.data,
      methods: me.methods,

      created () {
        if (window.$lib.env === 'product') {
          var resising = false

          this._originalData = JSON.parse(JSON.stringify(me.data))
          this._fitPage()

          window.addEventListener('resize', function () {
            if (!resising) {
              resising = true

              setTimeout(function () {
                me.vm._fitPage()
                resising = false
              }, 1000)
            }
          })
        }
      },

      mounted () {
        if (me.autoRunMethods) {
          for (let i = 0; i < me.autoRunMethods.length; i++) {
            this[me.autoRunMethods[i]]()
          }
        }

        if (me.intervalMethods) {
          for (let i = 0; i < me.intervalMethods.length; i++) {
            const method = me.intervalMethods[i]
            this.runIntervalMethod(method.time, method.name)
          }
        }

        if (typeof me.onloaded === 'function') {
          me.onloaded()
        }

        const vm = this
        setTimeout(function () { vm.$emit('loaded') }, 0)

        this.$forceUpdate()
      }
    })
  }

  Lib.prototype.createApi = function (apiConfig) {
    /* eslint-disable no-new-func */
    return function (eventParams) {
      this.$eventParams = eventParams || ''

      if (apiConfig.apiType === 'json') {
        return Promise.resolve(apiConfig.data)
      } else {
        const url = /^(http)|(https)/.test(apiConfig.url) ? apiConfig.url : window.$twApp.gateway + apiConfig.url
        let axiosConfig = {}

        try {
          axiosConfig = {
            url: this.parseValue(url),
            method: apiConfig.apiType === 'dataset' ? 'post' : apiConfig.apiMethod,
            headers: apiConfig.headersJson ? parseData.call(this, JSON.stringify(JSON.parse(apiConfig.headersJson))) : {},
            data: apiConfig.paramsJson ? parseData.call(this, JSON.stringify(JSON.parse(apiConfig.paramsJson))) : {}
          }

          if (apiConfig.requestFn) {
            const requestFn = (new Function('return ' + apiConfig.requestFn))()
            axiosConfig = requestFn.call(this, axiosConfig)
          }

          if (apiConfig.apiMethod === 'post' && !axiosConfig.data) {
            axiosConfig.data = {}
          }

          if (apiConfig.apiType === 'dataset') {
            axiosConfig.url = window.$twApp.datasetApiBaseUrl + '/dashboard/getAggregateData'

            axiosConfig.data.cfg.filters.forEach(item => {
              if (!Array.isArray(item.values)) {
                item.values = [item.values]
              }
            })

            axiosConfig.data.cfg = JSON.stringify(axiosConfig.data.cfg)
            axiosConfig.data = window.$ui.qs.stringify(axiosConfig.data)
          }
        } catch (e) {
          console.log(e)
        }

        return window.axios(axiosConfig).then((response) => {
          if (window.$lib.env === 'design') {
            apiConfig.responseDataJson = JSON.stringify(response.data, null, 2)
          }

          if (apiConfig.filterFn) {
            const filter = (new Function('return ' + apiConfig.filterFn))()
            this[apiConfig.libId][apiConfig.libApiName] = filter.call(this, response.data)
          } else {
            if (apiConfig.apiType === 'dataset' && apiConfig.datasetDataType === 'dataset') {
              if (Array.isArray(response.data.columnList) && Array.isArray(response.data.data)) {
                this[apiConfig.libId][apiConfig.libApiName] = dataSetToLibData(response.data)
              } else {
                this.$message({
                  type: 'error',
                  message: '数据集数据格式错误'
                })
              }
            } else {
              let sourceData = response.data

              if (apiConfig.apiType === 'dataset') {
                sourceData = flattenDataset(sourceData, apiConfig.datasetDataType)

                if (window.$lib.env === 'design') {
                  apiConfig.responseDataJson = JSON.stringify(sourceData, null, 2)
                }
              }

              const data = getDataByKey(sourceData, apiConfig.dataKey)

              const dataType = typeof data
              let dataTypeIsRight = (dataType === apiConfig.dataType)

              if (apiConfig.dataType === 'array' && Array.isArray(data)) {
                dataTypeIsRight = true
              }

              if (dataTypeIsRight) {
                this[apiConfig.libId][apiConfig.libApiName] = apiDataMapToLib(data, apiConfig)
              } else {
                Promise.reject(new TypeError('Data type is not valid.'))
              }
            }
          }

          this.$libRoot.$forceUpdate()

          if (apiConfig.tailingMethods) {
            for (let i = 0; i < apiConfig.tailingMethods.length; i++) {
              this[apiConfig.tailingMethods[i]]()
            }
          }
        }).catch(() => {
          // noop
          if (apiConfig.tailingMethods) {
            for (let i = 0; i < apiConfig.tailingMethods.length; i++) {
              this[apiConfig.tailingMethods[i]]()
            }
          }
        })
      }
    }
  }

  Lib.prototype.createMethod = function (data) {
    this.methods = { runMethod }
    this.autoRunMethods = []
    this.intervalMethods = []
    this.clearIntervalMethod()

    for (const LibId in data.apis) {
      if (data.apis.hasOwnProperty(LibId)) {
        const apis = data.apis[LibId]

        for (const apiKey in apis) {
          if (apis.hasOwnProperty(apiKey)) {
            const apiConfig = apis[apiKey]

            if (apiConfig.apiType !== 'pageParams') {
              if (apiConfig.autoSend) {
                this.autoRunMethods.push(apiConfig.methodName)
              }

              const interval = parseInt(apiConfig.interval)

              if (interval && interval > 0) {
                this.intervalMethods.push({
                  time: interval,
                  name: apiConfig.methodName
                })
              }

              this.methods[apiConfig.methodName] = this.createApi(apiConfig)
            }
          }
        }
      }
    }

    this.methods.parseValue = function (value) {
      if (typeof value === 'object') {
        const json = JSON.stringify(value)

        if (/(.*?){{(.*?)}}(.*?)/.test(json)) {
          return parseData.call(this.$libRoot, json)
        } else {
          return value
        }
      } else if (typeof value === 'string') {
        if (/(.*?){{(.*?)}}(.*?)/.test(value)) {
          try {
            return (new Function(`with(this.$libRoot){return ${value.replace(/^{{(.*?)}}$/gm, '$1').replace(/\["{{(.*?)}}"/gm, '[$1').replace(/(.*?){{(.*?)}}(.*?)/gm, `'$1'+($2)+'$3'`)}}`)).call(this)
          } catch (e) {
            return value
          }
        } else {
          return value
        }
      } else {
        return value
      }
    }
  }

  Lib.prototype.load = function (params) {
    this.members = params.libs
    this.loadedLibCount = 0
    this.el = params.el
    this.onloaded = params.onloaded

    if (typeof timeout === 'number') {
      this.timeout = params.timeout * 1000
    }

    for (let i = 0; i < this.members.length; i++) {
      const libName = this.members[i]

      if (this.loadedLibs.includes(libName)) {
        this.loadedLibCount++

        if (this.loadedLibCount >= this.members.length) {
          this.mounte()
        }
      } else {
        this.add(libName, params.libBaseUrl)
      }
    }
  }

  function parseData (jsonStr) {
    try {
      return (new Function(`with(this){return ${jsonStr.replace(/:"{{(.*?)}}"/gm, ':$1').replace(/\["{{(.*?)}}"/gm, '[$1').replace(/:"(.*?){{(.*?)}}(.*?)"/gm, ':"$1"+($2)+"$3"')}}`)).call(this)
    } catch (e) {
      JSON.parse(jsonStr)
    }
  }

  function getDataByKey (source, propChain) {
    if (propChain === '') {
      return source
    }

    const names = propChain.split('.')

    try {
      for (let i = 0; i < names.length; i++) {
        source = source[names[i]]
      }
    } catch (e) {
      source = undefined
    }

    return source
  }

  function initFont (baseWidth) {
    var windowWidth = document.documentElement.clientWidth
    var baseFontSize = 37.5
    var widthRate = (windowWidth / parseInt(baseWidth)).toFixed(2)

    var rootFontSize = 0
    rootFontSize = parseFloat((widthRate * baseFontSize).toFixed(1))

    document.documentElement.style.fontSize = rootFontSize + 'px'
  }

  function parsePageParams (params) {
    try {
      return (new Function(window.decodeURIComponent(window.atob(params))))()
    } catch (e) {
      console.error(e)
    }
  }

  function apiDataMapToLib (data, apiConfig) {
    let result = data

    if (Array.isArray(data)) {
      result = []

      data.forEach(item => {
        if (typeof item === 'object' && item !== null) {
          result.push(k2k(item, apiConfig))
        } else {
          result.push(item)
        }
      })
    } else if (typeof data === 'object' && data !== null) {
      result = k2k(data, apiConfig)
    }

    return result
  }

  function k2k (obj, apiConfig) {
    const result = {}

    for (let i = 0; i < apiConfig.apiTableMap.length; i++) {
      const apiKey = apiConfig.apiTableMap[i]
      const dataKey = apiConfig.dataTableMap[i]
      setPropChain(result, dataKey, getPropChain(obj, apiKey))
    }

    return Object.assign(obj, result)
  }

  function getPropChain (source, propChain) {
    if (propChain === '') {
      return source
    }

    let names = propChain.split('.')

    try {
      for (let i = 0; i < names.length; i++) {
        source = source[names[i]]
      }
    } catch (e) {
      source = undefined
    }

    return source
  }

  function setPropChain (source, propChain, value) {
    if (propChain === '') return

    let names = propChain.split('.')
    let length = names.length - 1

    try {
      for (let i = 0; i < length; i++) {
        if (!source[names[i]]) {
          source[names[i]] = {}
        }

        source = source[names[i]]
      }

      source[names[length]] = value
    } catch (e) {
      console.log(e)
    }
  }

  function dataSetToLibData (data) {
    const firstRow = data.columnList.map(item => item.name)
    data.data.unshift(firstRow)
    return data.data
  }

  function flattenDataset (data, type) {
    const result = []

    if (type === 'row') {
      for (let i1 = 0; i1 < data.data.length; i1++) {
        const subData = data.data[i1]
        const item = {}

        for (let i2 = 0; i2 < subData.length; i2++) {
          item[`col${i2}`] = subData[i2]
        }

        result.push(item)
      }
    } else if (type === 'column') {
      for (let i1 = 0; i1 < data.columnList.length; i1++) {
        const item = {}

        for (let i2 = 0; i2 < data.data.length; i2++) {
          item[`row${i2}`] = data.data[i2][i1]
        }

        result.push(item)
      }
    } else if (type === 'only-col') {
      for (let i1 = 0; i1 < data.data.length; i1++) {
        const subData = data.data[i1]

        for (let i2 = 0; i2 < subData.length; i2++) {
          result.push({
            name: data.columnList[i2].name,
            value: subData[i2]
          })
        }
      }
    } else if (type === 'table-head') {
      for (let i1 = 0; i1 < data.columnList.length; i1++) {
        result.push({
          label: data.columnList[i1].name,
          prop: `col${i1}`
        })
      }
    }

    return result
  }

  function runMethod (methodString, eventParams, options) {
    if (options) {
      options = JSON.parse(window.decodeURIComponent(atob(options)))

      if (options.globalParams) {
        for (let i = 0; i < options.globalParams.length; i++) {
          const param = options.globalParams[i]

          if (typeof param.value === 'string') {
            const m = param.value.match(/^%(\d+\.?\d*)%$/)

            if (m && m[1]) {
              setPropChain(this, param.key, parseFloat(m[1]))
            } else {
              setPropChain(this, param.key, param.value)
            }
          } else {
            setPropChain(this, param.key, param.value)
          }
        }
      }

      if (options.handler) {
        try {
          const mfn = (new Function('return ' + options.handler))()
          mfn.call(this, eventParams)
        } catch (e) {
          // noop
        }
      }
    }

    if (methodString) {
      const methods = methodString.split(',')

      for (let i = 0; i < methods.length; i++) {
        this[methods[i]](eventParams)
      }
    }
  }

  var _postDisposable = true

  window.Vue.mixin({
    updated: function() {
      if (_postDisposable && document.body) {
        window.top.postMessage(document.body.scrollHeight, '*')
        _postDisposable = false

        setTimeout(function () {
          window.top.postMessage(document.body.scrollHeight, '*')
          _postDisposable = true
        }, 200)
      }
    }
  })

  window.$lib = new Lib()

  Object.defineProperty(window.Vue.prototype, '$libRoot', {
    get () { return window.$lib.vm }
  })
})()
